using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Web;
using Higgs.Web.AtomPub.Configurations;
using Higgs.Web.AtomPub.Models;
using System.Text.RegularExpressions;
using System.IO;

namespace Higgs.Web.AtomPub
{
    public class BaseEntryRepository<TBlogContext, TEntry, TCategory, TMediaItem> : IEntryRepository
        where TBlogContext : IBlogContext<TEntry, TCategory, TMediaItem>
        where TEntry : class, IEntry
        where TCategory : class, ICategory
        where TMediaItem : class , IMediaItem
    {
        private TBlogContext DataContext { get; set; }
        
        public BaseEntryRepository()
        {
            DataContext = Activator.CreateInstance<TBlogContext>();
        }

        public virtual IEntry GetEntry(Guid id, IIdentity user)
        {
            return DataContext.Entries.Single(x => x.Id == id);
        }

        public virtual IEnumerable<IEntry> GetEntryList(int index, int count, bool includeDrafts, IIdentity user)
        {
            var entries = DataContext.Entries.AsQueryable();

            if(!includeDrafts) entries = entries.Where(x => !x.Draft);
            
            entries = entries.OrderByDescending(x => x.EditedDate);

            return entries.Skip(index).Take(count).ToList();
        }

        public IEnumerable<string> GetCategories(IIdentity user)
        {
            return DataContext.Categories.Select(x => x.Name);
        }

        public virtual DateTime? GetLastUpdated(bool includeDrafts, IIdentity user)
        {
            return DataContext.Entries.Where(x => x.Draft == includeDrafts)
                        .OrderByDescending(x => x.UpdatedDate)
                        .Select(x => x.UpdatedDate)
                        .FirstOrDefault();
        }

        public virtual int Count(IIdentity user)
        {
            return DataContext.Entries.Count();
        }

        public virtual IEnumerable<string> GetMediaPathsInEntry(IEntry entry, HttpContextBase context)
        {
            var setting = AtomPubConfigSection.Current;
            var pattern = new Regex(HttpUtility.UrlDecode(context.ToAbsoluteUrl("~/")) + setting.Media.StaticPath + @"/([0-9]{4}/[0-9]{1,2}/.*?\.[a-z]{3})", RegexOptions.IgnoreCase);
            var mediaIds = pattern.Matches(entry.ContentHtml)
                                    .OfType<Match>()
                                    .Select(x => "~/" + setting.Media.StaticPath + "/" + x.Groups[1].Value)
                                    .Distinct();

            return mediaIds;
        }

        // TODO: Create checkpoint before commit all changes
        public virtual void CreateEntry(IEntry entry, HttpContextBase context, IIdentity user)
        {
            entry.UpdatedDate = DateTime.Now;
            entry.EditedDate = DateTime.Now;
            if(!entry.Draft) entry.PublishedDate = DateTime.Now;

            DataContext.Entries.AddObject((TEntry)entry);
            DataContext.SaveChanges();

            foreach(var mediaPath in GetMediaPathsInEntry(entry, context))
            {
                var mediaItem = DataContext.MediaItems.SingleOrDefault(x => x.LogicalPath == mediaPath);
                
                if(mediaItem == null) continue;
                mediaItem.EntryId = entry.Id;
            }
            DataContext.SaveChanges();
        }

        public virtual void UpdateEntry(HttpContextBase context, Guid id, string title, string contentHtml, DateTime? published, string[] categories, bool draft, IIdentity user)
        {
            var entry = GetEntry(id, user);
            entry.Title = title;
            entry.ContentHtml = contentHtml;

            var mediaItems = DataContext.GetMediaItemCollection((TEntry)entry);
            var mediaItemList = mediaItems.ToList();
            var mediaPaths = GetMediaPathsInEntry(entry, context);
            
            // New Media Item.
            var mediaItemPathList = mediaItemList.Select(x => x.LogicalPath);
            foreach (var newMediaPath in mediaPaths.Where(x => !mediaItemPathList.Contains(x)))
            {
                var path = newMediaPath;
                mediaItems.Add(DataContext.MediaItems.Single(x => x.LogicalPath == path));
            }

            // Unused Media Item.
            foreach(var unusedItem in mediaItemList.Where(x => !mediaPaths.Contains(x.LogicalPath)))
            {
                var filePath = context.Server.MapPath(unusedItem.LogicalPath);
                if(File.Exists(filePath)) File.Delete(filePath);

                DataContext.MediaItems.DeleteObject(unusedItem);
                mediaItems.Remove(unusedItem);
            }

            if(published.HasValue)
            {
                entry.PublishedDate = published;
                entry.Draft = draft;
                
                // TODO: fix categories
                foreach(var c in categories)
                {
                    var c1 = c;
                    var cItem = DataContext.Categories.SingleOrDefault(x => x.Name.Equals(c1, StringComparison.CurrentCultureIgnoreCase));

                    if(cItem == null)
                    {
                        var newCat = Activator.CreateInstance<TCategory>();
                        newCat.Name = c;
                        DataContext.Categories.AddObject(newCat);
                    }
                }
            }

            DataContext.SaveChanges();
        }
        
        public virtual void DeleteEntry(Guid id, HttpContextBase context, IIdentity user)
        {
            var entry = GetEntry(id, user);

            // Delete all related media file.
            var deletedDirs = new List<string>();
            foreach(var mediaItem in DataContext.GetMediaItemCollection((TEntry) entry))
            {
                var filePath = context.Server.MapPath(mediaItem.LogicalPath);
                var dirPath = Path.GetDirectoryName(filePath);

                if(File.Exists(filePath)) File.Delete(filePath);
                if(!deletedDirs.Contains(dirPath)) deletedDirs.Add(dirPath);
            }

            // Automatically remove empty folder.
            deletedDirs.ForEach(dir => RecusiveRemoveEmptyFolder(new DirectoryInfo(dir)));

            DataContext.Entries.DeleteObject((TEntry)entry);
            DataContext.SaveChanges();
        }

        private void RecusiveRemoveEmptyFolder(DirectoryInfo dir)
        {
            if(!dir.GetFiles().Any() && !dir.GetDirectories().Any())
            {
                dir.Delete();

                RecusiveRemoveEmptyFolder(dir.Parent);
            }
        }
    }
}